#include "jni.h"
#include "NavMesh.h"
#include "platform.h"

static std::tr1::unordered_map<int, NavMesh*> navMeshMap;
static int navMeshIdIncrement;
static Vector3 straightPathPoints[Max_Straight_Path_Size];

#ifdef WIN32
#define csprintf _snprintf
#else
#define csprintf snprintf
#end if

JNIEXPORT jint JNICALL Java_RecastLib_loadFromPath(JNIEnv * env, jobject jobj, jstring path, jint maxNodes, jfloatArray jExtents, jfloatArray jWideExtents)
{
	navMeshIdIncrement++;
	int navMeshId = navMeshIdIncrement;
	char buffer[256];
	buffer[255] = 0;
	const char *str = env->GetStringUTFChars(path, 0);
	csprintf(buffer, 255, "%s", str);
	env->ReleaseStringUTFChars(path, str);

	float extents[3] = { 0 };
	float wideExtents[3] = { 0 };
	env->GetFloatArrayRegion(jExtents, 0, 3, extents);
	env->GetFloatArrayRegion(jWideExtents, 0, 3, wideExtents);

	NavMesh* navMesh = new NavMesh();
	bool success = navMesh->Create(buffer, maxNodes, extents, wideExtents);
	if (success)
	{
		navMeshMap[navMeshId] = navMesh;

		return jint(navMeshId);
	}
	else
	{
		delete navMesh;
	}

	return jint(0);
}

JNIEXPORT jint JNICALL Java_RecastLib_loadFromData(JNIEnv * env, jobject jObj, jbyteArray jByteDataArray, jint maxNodes, jfloatArray jExtents, jfloatArray jWideExtents)
{
	navMeshIdIncrement++;
	int navMeshId = navMeshIdIncrement;

	jbyte* jByteDataPointer = (jbyte*)env->GetByteArrayElements(jByteDataArray, 0);
	jsize length = env->GetArrayLength(jByteDataArray);

	float extents[3] = { 0 };
	float wideExtents[3] = { 0 };
	env->GetFloatArrayRegion(jExtents, 0, 3, extents);
	env->GetFloatArrayRegion(jWideExtents, 0, 3, wideExtents);

	NavMesh* navMesh = new NavMesh();
	bool success = navMesh->Create((uint8*)jByteDataPointer, length, maxNodes, extents, wideExtents);

	if (success)
	{
		navMeshMap[navMeshId] = navMesh;
		return jint(navMeshId);
	}
	else
	{
		delete navMesh;
		env->ReleaseByteArrayElements(jByteDataArray, jByteDataPointer, 0);
	}

	return jint(0);
}

JNIEXPORT jobject JNICALL Java_RecastLib_findStraightPath(JNIEnv* env, jobject jobj, jint navMeshId, jfloat x1, jfloat y1, jfloat z1, jfloat x2, jfloat y2, jfloat z2)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator navMeshIterator = navMeshMap.find(navMeshId);
	if (navMeshIterator == navMeshMap.end())
	{
		return nullptr;
	}

	NavMesh* navMesh = navMeshIterator->second;

	float start[3] = { x1,y1,z1 };
	float end[3] = { x2,y2,z2 };

	int straightPathPointCount = navMesh->FindStraightPath(start, end, straightPathPoints);

	if (straightPathPointCount > 0)
	{
		jclass class_arraylist = env->FindClass("java/util/ArrayList");
		jmethodID hashmap_construct_method = env->GetMethodID(class_arraylist, "<init>", "()V");
		jobject obj_hashmap = env->NewObject(class_arraylist, hashmap_construct_method, "");

		jmethodID arraylist_add_method = env->GetMethodID(class_arraylist, "add", "(Ljava/lang/Object;)Z");
		for (int i = 0; i < straightPathPointCount; ++i)
		{
			jfloat jFloatArray[3] = { straightPathPoints[i].x,straightPathPoints[i].y,straightPathPoints[i].z };
			jfloatArray args = (env)->NewFloatArray(3);
			env->SetFloatArrayRegion(args, 0, 3, jFloatArray);
			env->CallBooleanMethod(obj_hashmap, arraylist_add_method, args);
		}

		return obj_hashmap;
	}

	return nullptr;
}

JNIEXPORT jfloatArray JNICALL Java_RecastLib_getPolyHeight(JNIEnv* env, jobject jobj, jint navMeshId, jfloat x, jfloat y, jfloat z)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator navMeshIterator = navMeshMap.find(navMeshId);
	if (navMeshIterator != navMeshMap.end())
	{
		NavMesh* navMesh = navMeshIterator->second;
		if (navMesh != nullptr)
		{
			float height = 0.0f;
			if (navMesh->GetPolyHeight(x, y, z, &height))
			{
				jfloat heightArray[1];
				heightArray[0] = height;
				jfloatArray jHeightArray = env->NewFloatArray(1);
				env->SetFloatArrayRegion(jHeightArray, 0, 1, heightArray);

				return jHeightArray;
			}
		}
	}

	return nullptr;
}

JNIEXPORT jfloatArray JNICALL Java_RecastLib_findNearestPoly(JNIEnv* env, jobject jobj, jint navMeshId, jfloat x, jfloat y, jfloat z, jboolean jUseWideExtents)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator navMeshIterator = navMeshMap.find(navMeshId);
	if (navMeshIterator != navMeshMap.end())
	{
		NavMesh* navMesh = navMeshIterator->second;
		if (navMesh != nullptr)
		{
			float resultPoint[3];
			if (navMesh->FindNearestPoly(x, y, z, jUseWideExtents, resultPoint))
			{
				jfloat jResultPoint[3];
				jResultPoint[0] = resultPoint[0];
				jResultPoint[1] = resultPoint[1];
				jResultPoint[2] = resultPoint[2];
				jfloatArray jResultArray = env->NewFloatArray(3);
				env->SetFloatArrayRegion(jResultArray, 0, 3, jResultPoint);

				return jResultArray;
			}
		}
	}

	return nullptr;
}

JNIEXPORT jboolean JNICALL Java_RecastLib_raycast(JNIEnv* env, jobject jobj, jint navMeshId, jfloat x1, jfloat y1, jfloat z1, jfloat x2, jfloat y2, jfloat z2)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator navMeshIterator = navMeshMap.find(navMeshId);
	if (navMeshIterator != navMeshMap.end())
	{
		NavMesh* navMesh = navMeshIterator->second;
		if (navMesh != nullptr)
		{
			if (navMesh->Raycast(x1, y1, z1, x2, y2, z2))
			{
				return JNI_TRUE;
			}
		}
	}

	return JNI_FALSE;
}

JNIEXPORT jfloatArray JNICALL Java_RecastLib_moveAlongSurface(JNIEnv* env, jobject jobj, jint navMeshId, jfloat x1, jfloat y1, jfloat z1, jfloat x2, jfloat y2, jfloat z2)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator navMeshIterator = navMeshMap.find(navMeshId);
	if (navMeshIterator != navMeshMap.end())
	{
		NavMesh* navMesh = navMeshIterator->second;
		if (navMesh != nullptr)
		{
			float resultPoint[3];
			if (navMesh->MoveAlongSurface(x1, y1, z1, x2, y2, z2, resultPoint))
			{
				jfloat jResultPoint[3];
				jResultPoint[0] = resultPoint[0];
				jResultPoint[1] = resultPoint[1];
				jResultPoint[2] = resultPoint[2];
				jfloatArray jResultArray = env->NewFloatArray(3);
				env->SetFloatArrayRegion(jResultArray, 0, 3, jResultPoint);

				return jResultArray;
			}
		}
	}

	return nullptr;
}

JNIEXPORT void JNICALL Java_RecastLib_release(JNIEnv *, jobject, jint navMeshId)
{
	std::tr1::unordered_map<int, NavMesh*>::iterator iter = navMeshMap.find(navMeshId);
	if (iter != navMeshMap.end())
	{
		NavMesh* navMesh = iter->second;
		navMeshMap.erase(iter);
		delete navMesh;
	}
}

static JNINativeMethod s_methods[] =
{
	{ "loadFromPath","(Ljava/lang/String;I[F[F)I",(void*)Java_RecastLib_loadFromPath },
	{ "loadFromData","([BI[F[F)I",(void*)Java_RecastLib_loadFromData },
	{ "findStraightPath","(IFFFFFF)Ljava/util/List;",(void*)Java_RecastLib_findStraightPath },
	{ "getPolyHeight","(IFFF)[F",(void*)Java_RecastLib_getPolyHeight},
	{ "findNearestPoly","(IFFFZ)[F",(void*)Java_RecastLib_findNearestPoly },
	{ "raycast","(IFFFFFF)Z",(void*)Java_RecastLib_raycast },
	{ "moveAlongSurface","(IFFFFFF)[F",(void*)Java_RecastLib_moveAlongSurface },
	{ "release","(I)I",(void*)Java_RecastLib_release },
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
	JNIEnv* env = nullptr;
	if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
	{
		return JNI_ERR;
	}
	jclass cls = env->FindClass("Lcom/linekong/mmo/navmesh/RecastLib;");
	if (cls == nullptr)
	{
		return JNI_ERR;
	}
	int len = sizeof(s_methods) / sizeof(s_methods[0]);
	if (env->RegisterNatives(cls, s_methods, len) < 0)
	{
		return JNI_ERR;
	}

	return JNI_VERSION_1_6;
}
